package com.itcast.core;

import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 核心beanFactory实现
 * @作者 itcast
 * @创建日期 2020/10/10 15:18
 **/
public class DefaultListableBeanFactory implements BeanFactory{

    // 用于存放bean的描述 key: beanName    value: bean的描述对象
    private Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    // 用于存放单例对象的集合
    private Map<String,Object> singletonObjects = new ConcurrentHashMap<>();

    public Object getBean(String beanName) {
        // 1. 先判断单例集合中是否有该对象
        Object singletonObj = singletonObjects.get(beanName);
//                有返回,没有下一步
        if(singletonObj !=null){
            return singletonObj;
        }
        // 2. 判断是否有父工厂且父工厂是否包含该对象
        //        有调用父工厂的getBean返回
        // 3. 尝试自己创建，从beanDefinitionMap中获取bean的定义
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if(beanDefinition == null){
            //        没有报异常
            throw new RuntimeException("NoSuchBeanDefinition  未找到bean定义");
        }
        // 4. 判断bean定义的scope属性
        String scope = beanDefinition.getScope();
        if("singleton".equals(scope)){
//        单例，双检索在判断一次单例集合是否包含该对象  不包含，创建该对象，并存储到单例集合中
            synchronized (this.singletonObjects){
                Object obj = this.singletonObjects.get(beanName);
                if(obj == null){
                    obj = createBean(beanDefinition);
                    this.singletonObjects.put(beanName,obj);
                }
                return obj;
            }
        }else {
//        多例, 直接创建对象返回
            return createBean(beanDefinition);
        }
    }
    /**
     * 基于bean定义构建bean对象
     * @param beanDefinition
     * @return
     */
    public Object createBean(BeanDefinition beanDefinition){
        // 1. 根据配置中的className 获取 class对象
        String className = beanDefinition.getClassName();
        Class classz;
        try {
            classz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("类未找到 ClassNotFoundException");
        }
        // 2. 判断是否配置了 factory-method 工厂方法
        // 配置了 通过反射调用工厂方法获取对象
        // 3. 判断是否配置了构造器参数
        // 配置了 匹配对应的构造器 通过反射调用工厂方法
        // 4. 通过反射调用默认构造器构建对象
        Object obj;
        try {
            obj  = classz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
            throw new RuntimeException("初始化对象 异常");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("非法访问 异常");
        }
        // 5. 依赖注入
        List<Property> propertyList = beanDefinition.getPropertyList();
        try {
            for (Property property : propertyList) {
                String name = property.getName();
                String value = property.getValue();
                String ref = property.getRef();
                if(name !=null && !"".equals(name)){
                    if(value!=null && !"".equals(value)){
                        // 注入value的值
                        // 目标对象    属性名称   属性值
                        BeanUtils.setProperty(obj,name,value);
                    }
                    if(ref!=null && !"".equals(ref)){
                        // 注入ref的值 ref引用的是IOC容器中其它bean对象  接着getBean获取
                        BeanUtils.setProperty(obj,name,getBean(ref));
                    }
                }

            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("非法访问 异常");
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            throw new RuntimeException("方法调用 异常");
        }
        return obj;
    }
    /**
     * 注册bean定义到容器
     * @param beanDefinition
     */
    public void registryBeanDefinition(BeanDefinition beanDefinition){
        this.beanDefinitionMap.put(beanDefinition.getBeanName(),beanDefinition);
    }

    public void preInstaniceSingletons(){
        this.beanDefinitionMap.forEach((key,val) -> {
            if("singleton".equals(val.getScope())){
                this.getBean(key);
            }
        });
    }
}
